package slap;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import jodd.util.StringUtil;

import slap.annotation.AfterAppInit;
import slap.annotation.AfterFrameworkInit;
import slap.annotation.AfterSystemInit;
import slap.app.Application;
import slap.berkeleydb.BDB;
import slap.concurrent.Invoker;
import slap.mvc.result.RenderStatic;
import slap.server.NettyServer;
import slap.template.Template;

import app.blog.Blog;


public class Slap {
	
	private static final Logger logger = LoggerFactory.getLogger(Slap.class);
	
    public static final String VERSION = "0.1.0";
    
	public static final String ENCODING = "UTF-8";
	
	public static String secretKey = "secretKey";
	
	public static Properties props;
	
	public static String baseDomain;
	
	public static int port;
	
	public static Map<String, Application> applications = new HashMap<String, Application>();


	public static Application defaultApplication;
	
	public static void shutdown(int exitCode){
		System.exit(exitCode);
	}
	
	public static void shutdown(){
		System.exit(0);
	}
	
	public static void initFramework(){
		props = new Properties();
		try {
			props.load(new FileInputStream(Resources.Presets.DIR_ETC+"slap.conf"));
		} catch (Exception e) {
			e.printStackTrace();
			shutdown(1);
		}
		String baseDomainRaw = props.getProperty(PropsNames.SERVER_DOMAIN_BASE);
		if(StringUtil.isNotBlank(baseDomainRaw)){
			baseDomainRaw = baseDomainRaw.trim();
			if(baseDomainRaw.endsWith(".")){
				logger.error("{} in slap.conf config[{}] error: no allowed ends with '.'", PropsNames.SERVER_DOMAIN_BASE,  baseDomainRaw);
			}
			baseDomain = baseDomainRaw;
		}else{
			baseDomain = null;
		}
		
		port = Integer.valueOf(props.getProperty(PropsNames.SERVER_PORT, "9999"));
		new NettyServer().start();
	}
	
	public static void initWebApps(){
		
		String apps = props.getProperty("applications");
		if(StringUtil.isEmpty(apps)){
			logger.error("No Application defined.");
			shutdown(1);
		}
		for(String appClassName : apps.split(",")){
			appClassName = appClassName.trim();
			Class<? extends Application> appClass = null;
			Application app = null;
			try {
				appClass = (Class<? extends Application>) Class.forName(appClassName);
				app = appClass.newInstance();
				
			}catch(Exception e){
				e.printStackTrace();
				logger.error("Application:[{}] failed to start.", app.getAppName());
			}
			if(app == null){
				logger.error("Application:[UNKNOW] failed to start.");
			}else{
				try {
					appClass.getField("self").set(null, app);
				} catch (Exception e) {
					e.printStackTrace();
					logger.error("Application:[UNKNOW] failed to start.", e);
					shutdown(1);
				}
				applications.put(app.domain, app);
				afterAppInit(app);
				logger.info("Application:[{}] success to start.", app.getAppName());
			}
		}
		if(applications.size() < 1){
			logger.error("No proper Application defined, or Application start failed.");
			shutdown(1);
		}
		
	}
	
	public static void init(){
		logger.info("Slap server is starting...");
		Resources.init();
		initFramework();
		afterFrameworkInit();
		initWebApps();
		afterSystemInit();
		logger.info("Slap server start completed.");
	}
	
	private static void afterSystemInit() {
		List<Method> afters = Resources.ManagedMethods.withAnnotation(AfterSystemInit.class);
		if(afters != null){
			for(Method after : afters){
				try {
					after.invoke(null);
				} catch (IllegalArgumentException e) {
					e.printStackTrace();
				} catch (IllegalAccessException e) {
					e.printStackTrace();
				} catch (InvocationTargetException e) {
					e.printStackTrace();
				}
			}
		}
		Resources.ManagedMethods.cleanup();
	}
	
	private static void afterAppInit(Application app) {
		List<Method> afters = Resources.ManagedMethods.withAnnotation(AfterAppInit.class);
		if(afters != null){
			for(Method after : afters){
				if(!after.getAnnotation(AfterAppInit.class).value().equals(app.getAppName())){
					continue;
				}
				try {
					after.invoke(null);
				} catch (IllegalArgumentException e) {
					e.printStackTrace();
				} catch (IllegalAccessException e) {
					e.printStackTrace();
				} catch (InvocationTargetException e) {
					e.printStackTrace();
				}
			}
		}
	}
	
	private static void afterFrameworkInit() {
		List<Method> afters = Resources.ManagedMethods.withAnnotation(AfterFrameworkInit.class);
		if(afters != null){
			for(Method after : afters){
				try {
					after.invoke(null);
				} catch (IllegalArgumentException e) {
					e.printStackTrace();
				} catch (IllegalAccessException e) {
					e.printStackTrace();
				} catch (InvocationTargetException e) {
					e.printStackTrace();
				}
			}
		}
		
		RenderStatic.init();
	}

	public static void main(String[] args) throws Exception {
		long startTime = System.currentTimeMillis();
		init();
    	logger.info("Server started in {} ms.", System.currentTimeMillis()-startTime);
    }
	
}
